package com.telenav.snowsweeper.renderer;

import java.util.Vector;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Rect;

import com.telenav.snowsweeper.R;
import com.telenav.snowsweeper.data.Edge;
import com.telenav.snowsweeper.data.Point;
import com.telenav.snowsweeper.data.Route;
import com.telenav.snowsweeper.framework.ui.IObjectRenderer;
import com.telenav.snowsweeper.util.DisplayUtil;
import com.telenav.snowsweeper.util.ResourceUtil;

public class MapEdgeRenderer implements IObjectRenderer {

	private RendererParams params;
	private RendererParams current;
	private MapEdgeBuilder builder;
	
	private Paint edgePaint;
	private Paint timePaint;
	
	// cache data for renderer pre frame  
	private int arrow_offset;
	private int arrow_counter;
	private int arrow_half_size;
	
	public MapEdgeRenderer(MapEdgeBuilder builder) {
		this.builder = builder;
		this.params = new RendererParams();
		
		edgePaint = new Paint();
		edgePaint.setColor(DisplayUtil.COLOR_VALID_EDGE);
		edgePaint.setStyle(Paint.Style.STROKE);
		edgePaint.setStrokeCap(Cap.ROUND);
		
		timePaint = new Paint();
		timePaint.setColor(DisplayUtil.COLOR_TIME_INFO);
	}
	
	public RendererParams getRenderParams() {
		return params;
	}
	
	public RendererParams getConstRenderParams() {
		return current;
	}

	@Override
	public void build(long timeStamp) {
		params.dirty = true;
		if (params.dirty || current == null) {
			current = params.clone();
			builder.build(current, timeStamp);
			params.timeStamp = current.timeStamp;
		}		
	}
	
	private int edgeWidth;
	private int fontSize;
	@Override
	public void render(Canvas canvas, Layer layer) {
		if (layer == Layer.Edge) {
			edgeWidth = Util.mapDist2Screen(DisplayUtil.EDGE_WIDTH, params.scaleX);		// FIXME
			edgeWidth = Util.getValue(edgeWidth, DisplayUtil.EDGE_MAX_WIDTH_PX, DisplayUtil.EDGE_MIN_WIDTH_PX);
			edgePaint.setStrokeWidth(edgeWidth);
			
			fontSize = Util.mapDist2Screen(DisplayUtil.FONT_WIDTH, params.scaleX);		// FIXME
			fontSize = Util.getValue(fontSize, DisplayUtil.FONT_MAX_WIDTH_PX, DisplayUtil.FONT_MIN_WIDTH_PX);
			timePaint.setTextSize(fontSize);

			boolean initArrowData = false;
			int[] head, tail;
			long timeStamp = current.timeStamp;
			for (Edge edge : current.edgesOnScreen) {
				head = edge.getBegin().getScreenXY(timeStamp);
				tail = edge.getEnd().getScreenXY(timeStamp);
				if (head == null || tail == null)
					continue;
				if (!initArrowData) {
					initArrowData = true;
					int d = Math.max(Math.abs(head[0] - tail[0]), Math.abs(head[1] - tail[1]));
					int arrowWidth = ResourceUtil.loadImage(R.drawable.up).getWidth();
					arrow_counter = (d + (arrowWidth >> 1)) / arrowWidth >> 1;
					arrow_offset = d - arrow_counter * arrowWidth >> 1;
					arrow_half_size = arrowWidth >> 1;
				}
				drawEdge(canvas, head, tail, edge.getPosTransTime(), edge.getNegTransTime());
			}
		}
		else if (layer == Layer.SNOW) {
			drawSnow(canvas);
		}
		else if (layer == Layer.PASSPOINT) {
			drawPassPoints(canvas);
		}
	}
	
	private void drawEdge(Canvas canvas, int[] start, int[] end, int posT, int negT) {
		if (posT <= 0 && negT <= 0)
			return ;
		canvas.drawLine(start[0], start[1], end[0], end[1], edgePaint);
		boolean horiz = start[1] == end[1];
		if (negT <= 0) {
			drawSingleDirectionEdge(canvas, start, end, ResourceUtil.loadImage(horiz ? R.drawable.right : R.drawable.down), horiz);
		}
		else if (posT <= 0) {
			drawSingleDirectionEdge(canvas, start, end, ResourceUtil.loadImage(horiz ? R.drawable.left : R.drawable.up), horiz);
		}
		
		int time = Math.max(posT, negT); 
		int x = start[0] + end[0] >> 1;
		int y = start[1] + end[1] >> 1;
		if (horiz) {
			canvas.drawText("" + time, x, y - fontSize, timePaint);
		}
		else {
			if (time >= 100)
				x -= fontSize * 3;
			else if (time >= 10)
				x -= fontSize << 1;
			else 
				x -= fontSize;
			canvas.drawText("" + time, x, y, timePaint);
		}
	}
	
	private void drawSingleDirectionEdge(Canvas canvas, int[] start, int[] end, Bitmap bitmap, boolean horiz) {
		Rect r = new Rect();
		r.left = start[0] + (horiz ? arrow_offset : -arrow_half_size);
		r.top = start[1] + (horiz ? -arrow_half_size : arrow_offset);
		r.right = r.left + bitmap.getWidth();
		r.bottom = r.top + bitmap.getHeight();
		for (int i = 0; i < arrow_counter; ++i) {
			canvas.drawBitmap(bitmap, null, r, edgePaint);
			if (horiz) {
				r.left = r.right;
				r.right += bitmap.getWidth();
			}
			else {
				r.top = r.bottom;
				r.bottom += bitmap.getHeight();
			}
		}
	}
	
	public static class RendererParams {
		// fixed-point number, have 6 bits decimal.
	    public static final int SCALE_STEP = 1;
		int scaleX = 64;
		int scaleY = 64;
		int centerX = 0;
		int centerY = 0;
		Vector<Point> passPoints = new Vector<Point>();
		
		Vector<Edge> edgesOnScreen = new Vector<Edge>();
		int edgeWidth = -1;
		long timeStamp = -1;
		boolean dirty = true;
		
		public synchronized void setScaleXY(int sx, int sy) {
			this.scaleX = sx;
			this.scaleY = sy;
			dirty = true;
		}
		
		public synchronized void adjustScaleXY(int dx, int dy) {
			this.scaleX += dx;
			this.scaleY += dy;
			if (scaleX < Util.EDGE_MIN_SCALE)
				scaleX = Util.EDGE_MIN_SCALE;
			else if (scaleX > Util.EDGE_MAX_SCALE)
				scaleX = Util.EDGE_MAX_SCALE;
			if (scaleY < Util.EDGE_MIN_SCALE)
				scaleY = Util.EDGE_MIN_SCALE;
			else if (scaleY > Util.EDGE_MAX_SCALE)
				scaleY = Util.EDGE_MAX_SCALE;
			dirty = true;
		}
		
		public synchronized void move2(int cx, int cy) {
			this.centerX = cx;
			this.centerY = cy;
			dirty = true;
		}
	    
		public synchronized void screenMove2(int sx, int sy) {
		        int dx_px = Util.screenDist2Map((sx - centerX), scaleX);
		        int dy_px = Util.screenDist2Map((sy - centerY), scaleY);
		        this.centerX += dx_px;
	            this.centerY += dy_px;
	            if (centerX < Util.EDGE_MAP_MIN_X)
	                centerX = Util.EDGE_MAP_MIN_X;
	            else if (centerX > Util.EDGE_MAP_MAX_X)
	                centerX = Util.EDGE_MAP_MAX_X;
	            if (centerY < Util.EDGE_MAP_MIN_Y)
	                centerY = Util.EDGE_MAP_MIN_Y;
	            else if (centerY > Util.EDGE_MAP_MAX_Y)
	                centerY = Util.EDGE_MAP_MAX_Y;
	            dirty = true;
	    }
		
		public synchronized void move(int dx_px, int dy_px) {
			dx_px = Util.screenDist2Map(dx_px, scaleX);
			dy_px = Util.screenDist2Map(dy_px, scaleY);
			this.centerX += dx_px;
			this.centerY += dy_px;
			
			final int margin = 15;
			int halfScreenDx = Util.screenDist2Map(Util.canvasWidth >> 1, scaleX);
			int halfScreenDy = Util.screenDist2Map(Util.canvasHeight >> 1, scaleY);
			if (centerX - halfScreenDx < Util.EDGE_MAP_MIN_X - margin)
				centerX = Util.EDGE_MAP_MIN_X - margin + halfScreenDx;
			else if (centerX + halfScreenDx > Util.EDGE_MAP_MAX_X + margin)
				centerX = Util.EDGE_MAP_MAX_X + margin - halfScreenDx;
			
			if (centerY - halfScreenDy < Util.EDGE_MAP_MIN_Y - margin)
				centerY = Util.EDGE_MAP_MIN_Y - margin + halfScreenDy;
			else if (centerY + halfScreenDy > Util.EDGE_MAP_MAX_Y + margin)
				centerY = Util.EDGE_MAP_MAX_Y + margin - halfScreenDy;
			dirty = true;
		}
		
		public RendererParams clone() {
			RendererParams params = new RendererParams();
			synchronized (this) {
				params.scaleX = scaleX;
				params.scaleY = scaleY;
				params.centerX = centerX;
				params.centerY = centerY;
				params.passPoints = new Vector<Point>();
				params.passPoints.addAll(passPoints);
				timeStamp = -1;
				params.dirty = true;
				dirty = false;
			}
			return params;
		}
		
		public synchronized void addPassPoints(Vector<Point> points) {
			for (Point point : points) {
				if (!passPoints.contains(point)) {
					passPoints.add(point);
				}
			}
			dirty = true;
		}
		
		public synchronized void removePassPoints(Vector<Point> points) {
			passPoints.removeAll(points);
			dirty = true;
		}
		
		public synchronized void removePassPoints() {
            passPoints.clear();
            dirty = true;
        }
		
		public synchronized void copyPassPoints(Vector<Point> points) {
			points.addAll(passPoints);
		}
	}
	
	private void drawSnow(Canvas canvas) {
		Bitmap cloud = ResourceUtil.loadImage(R.drawable.cloudsmall);
		if (cloud == null)
			return;
		
		int offsetX = cloud.getWidth() >> 1;
		int offsetY = cloud.getHeight() >> 1;
		Rect dst = new Rect();
		
		int[] out_xy = new int[2];
		for (Edge edge : current.edgesOnScreen) {
			if (edge.getSnowNum() > 0) {
				edge.getMiddleOnScreen(out_xy, current.timeStamp);
				dst.left = out_xy[0] - offsetX;
				dst.top = out_xy[1] - offsetY;
				dst.right = dst.left + cloud.getWidth();
				dst.bottom = dst.top + cloud.getHeight();
				edgePaint.setAlpha(DisplayUtil.SNOW_SEMITRANSPARENT);
				canvas.drawBitmap(cloud, null, dst, edgePaint);
				edgePaint.setAlpha(0xff);
				
				/*out_xy[0] = dst.left;
				out_xy[1] = dst.bottom;
				dst.top = dst.bottom;
				dst.bottom = dst.top + snow.getHeight();
				snowNum = edge.getSnowNum();
				snowNum = Math.min(snowNum, DisplayUtil.DISPLAY_MAX_SNOW_LEVEL);
				offsetX = dst.width() - snow.getWidth() * snowNum >> 1;
				for (int i = 0; i < snowNum; ++i) {
					dst.left = offsetX;
					dst.right = dst.left + snow.getWidth();
					canvas.drawBitmap(snow, null, dst, p);
					offsetX += snow.getWidth();
				}*/
			}
		}
	}
	
	private void drawPassPoints(Canvas canvas) {
		Vector<Point> points = new Vector<Point>();
		current.copyPassPoints(points);
		Rect dst = new Rect();
		Bitmap passpoint = ResourceUtil.loadImage(R.drawable.passpoint);
		int[] xy;
		for (Point point : points) {
			xy = point.getScreenXY(current.timeStamp);
			dst.left = xy[0];
			dst.top = xy[1];
			dst.right = dst.left + passpoint.getWidth();
			dst.bottom = dst.top + passpoint.getHeight();
			canvas.drawBitmap(passpoint, null, dst, edgePaint);
		}
	}
}
